package com.egg.auth.service.menu.impl;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.egg.auth.entity.menu.Menu;
import com.egg.auth.entity.role.Role;
import com.egg.auth.entity.role.RoleLinkMenu;
import com.egg.auth.enums.menu.MenuTypeEnum;
import com.egg.auth.mapper.menu.MenuMapper;
import com.egg.auth.model.request.web.MenuPostOrPut;
import com.egg.auth.model.response.web.MenuTree;
import com.egg.auth.model.response.web.RouterTree;
import com.egg.auth.service.menu.IMenuService;
import com.egg.auth.service.role.IRoleLinkMenuService;
import com.egg.auth.service.role.IRoleService;
import com.egg.common.core.constant.CoreConstant;
import com.egg.common.core.constant.StringPool;
import com.egg.common.core.exception.CustomException;
import com.egg.common.mybatis.service.impl.BaseServiceImpl;
import com.egg.common.util.ConvertUtil;
import com.egg.common.util.JsonUtil;
import com.egg.common.util.OptionalUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;


@Slf4j
@Service
public class MenuServiceImpl extends BaseServiceImpl<MenuMapper, Menu> implements IMenuService {


    @Autowired
    private IRoleLinkMenuService roleLinkMenuService;
    @Autowired
    private IRoleService roleService;

    @Override
    public List<Menu> findAll() {
        LambdaQueryWrapper<Menu> qw = buildLambdaQw();
        qw.orderByAsc(Menu::getSort);
        return list(qw);
    }

    @Override
    public List<Menu> findByUserId(Serializable userId) {
        List<Role> roleList = roleService.findEnabledByUserId(userId);
        if (ObjectUtil.isEmpty(roleList)) {
            return Collections.emptyList();
        }
        if (roleService.hasAdmin(roleList) || roleService.hasWeb(roleList) || roleService.hasRead(roleList)) {
            // 超级管理员获取所有菜单
            // 后端管理员获取所有菜单
            // 游客获取所有菜单
            return findAll();
        }
        List<RoleLinkMenu> roleLinkMenuList = roleLinkMenuService.findByRoleId(roleList.stream().map(o -> o.getId()).collect(Collectors.toList()));
        if (ObjectUtil.isEmpty(roleLinkMenuList)) {
            return Collections.emptyList();
        }
        LambdaQueryWrapper<Menu> qw = buildLambdaQw();
        qw.orderByAsc(Menu::getSort);
        qw.in(Menu::getId, roleLinkMenuList.stream().map(o -> o.getMenuId()).collect(Collectors.toSet()));
        return list(qw);
    }

    @Override
    public List<MenuTree> convertMenuTree(List<Menu> menuList) {
        if (ObjectUtil.isEmpty(menuList)) {
            return Collections.emptyList();
        }
        List<Menu> topMenuList = menuList.stream()
                .filter(o -> Objects.equals(o.getPid(), CoreConstant.DEFAULT_ID))
                .collect(Collectors.toList());
        Map<Long, List<Menu>> childrenDeptGroup = menuList.stream()
                .filter(o -> !Objects.equals(o.getPid(), CoreConstant.DEFAULT_ID))
                .collect(Collectors.groupingBy(o -> o.getPid()));
        List<MenuTree> menuTree = new ArrayList<>();
        topMenuList.forEach(o -> {
            MenuTree topDeptTree = JsonUtil.objToObj(o, MenuTree.class);
            topDeptTree.setChildren(convertTree(o, childrenDeptGroup));
            menuTree.add(topDeptTree);
        });
        return menuTree;
    }

    private List<MenuTree> convertTree(Menu pMenu, Map<Long, List<Menu>> childrenMenuGroup) {
        List<Menu> childrenDept = childrenMenuGroup.getOrDefault(pMenu.getId(), Collections.emptyList());
        if (ObjectUtil.isEmpty(childrenDept)) {
            return Collections.emptyList();
        }
        List<MenuTree> menuTreeList = new ArrayList<>();
        childrenDept.forEach(o -> {
            MenuTree menuTree = JsonUtil.objToObj(o, MenuTree.class);
            menuTree.setChildren(convertTree(o, childrenMenuGroup));
            menuTreeList.add(menuTree);
        });
        return menuTreeList;
    }

    @Override
    public List<RouterTree> convertRouterTree(List<MenuTree> menuTreeList) {
        List<RouterTree> routers = new LinkedList<>();
        for (MenuTree menu : menuTreeList) {
            RouterTree router = new RouterTree();
            if (Objects.equals(MenuTypeEnum.CATALOG.getCode(), menu.getType())) {
                router.setName(menu.getPath());
            }
            router.setPath(menu.getPath());
            router.setComponent(menu.getComponent());
            router.setRedirect(menu.getRedirect());
            router.setHidden(menu.getHidden());
            RouterTree.Meta meta = new RouterTree.Meta();
            meta.setTitle(menu.getName());
            meta.setIcon(menu.getIcon());
            meta.setHidden(menu.getHidden());
            meta.setKeepAlive(false);
            // 存在子节点并且不隐藏
            boolean alwaysShow = ObjectUtil.isNotEmpty(menu.getChildren()) && !menu.getHidden();
            meta.setAlwaysShow(alwaysShow);
            router.setMeta(meta);
            List<RouterTree> cRouterTree = convertRouterTree(menu.getChildren());
            router.setChildren(cRouterTree);
            routers.add(router);
        }
        return routers;
    }

    private boolean existName(String name, Optional<? extends Serializable> pid) {
        return existName(name, pid, Optional.empty());
    }

    @Transactional(rollbackFor = Throwable.class)
    @Override
    public Long add(MenuPostOrPut param) {
        if (MenuTypeEnum.isInvalid(param.getType())) {
            throw new CustomException(String.format("类型[%s]无效", param.getType()));
        }
        if (existName(param.getName(), OptionalUtil.build(param.getPid()))) {
            throw new CustomException(String.format("名称[%s]已存在", param.getName()));
        }
        Menu menu = JsonUtil.objToObj(param, Menu.class);
        Optional<Menu> pMenu = baseFindById(param.getPid());
        if (pMenu.isPresent()) {
            if (ObjectUtil.isNotEmpty(pMenu.get().getAncestors())) {
                menu.setAncestors(pMenu.get().getAncestors() + StringPool.COMMA + menu.getPid());
            } else {
                menu.setAncestors(menu.getPid().toString());
            }
        } else {
            menu.setPid(CoreConstant.DEFAULT_ID);
            menu.setAncestors(CoreConstant.DEFAULT_ID.toString());
        }
        baseSave(menu);
        return menu.getId();
    }

    private List<Menu> findAllSubById(Collection<? extends Serializable> ids) {
        if (ObjectUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        LambdaQueryWrapper<Menu> qw = Wrappers.lambdaQuery();
        qw.and(wrapper -> {
            for (Serializable id : ids) {
                wrapper.or().apply(String.format("find_in_set(%s, ancestors)", id));
            }
        });
        return list(qw);
    }

    private List<Menu> findAllSubById(Serializable id) {
        if (ObjectUtil.isEmpty(id)) {
            return Collections.emptyList();
        }
        return findAllSubById(Arrays.asList(id));
    }

    /**
     * 查询当前ID是否属于它自己的子集
     *
     * @param id
     * @return
     */
    private boolean belongToSub(Serializable id) {
        if (ObjectUtil.isEmpty(id)) {
            return false;
        }
        Set<String> subIds = findAllSubById(id).stream()
                .map(o -> o.getId().toString()).collect(Collectors.toSet());
        return subIds.contains(id.toString());
    }

    private boolean existName(String name, Optional<? extends Serializable> pid, Optional<? extends Serializable> filterId) {
        if (ObjectUtil.isEmpty(name)) {
            return false;
        }
        LambdaQueryWrapper<Menu> qw = buildLambdaQw();
        qw.eq(Menu::getName, name);
        pid.ifPresent(o -> qw.eq(Menu::getPid, o));
        filterId.ifPresent(o -> qw.notIn(Menu::getId, o));
        return count(qw) > 0;
    }

    @Transactional(rollbackFor = Throwable.class)
    @Override
    public void edit(Serializable id, MenuPostOrPut param) {
        if (MenuTypeEnum.isInvalid(param.getType())) {
            throw new CustomException(String.format("类型[%s]无效", param.getType()));
        }
        Menu exist = baseGetById(id);
        if (existName(param.getName(), OptionalUtil.build(param.getPid()), Optional.of(exist.getId()))) {
            throw new CustomException(String.format("名称[%s]已存在", param.getName()));
        }
        Menu menu = JsonUtil.objToObj(param, Menu.class);
        menu.setId(exist.getId());
        Optional<Menu> pMenu = baseFindById(param.getPid());
        if (pMenu.isPresent()) {
            //不允许设置为当前子集的子集中的任何一个否则会产生循环
            if (belongToSub(id)) {
                throw new CustomException("不允许变更为自己的子菜单");
            }
            if (ObjectUtil.isNotEmpty(pMenu.get().getAncestors())) {
                menu.setAncestors(pMenu.get().getAncestors() + StringPool.COMMA + menu.getPid());
            } else {
                menu.setAncestors(menu.getPid().toString());
            }
        } else {
            menu.setPid(CoreConstant.DEFAULT_ID);
            menu.setAncestors(CoreConstant.DEFAULT_ID.toString());
        }
        baseSave(menu);
    }

    private List<Menu> findAllSubAndCurrentById(Collection<? extends Serializable> ids) {
        if (ObjectUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        LambdaQueryWrapper<Menu> qw = Wrappers.lambdaQuery();
        qw.and(wrapper -> {
            wrapper.in(Menu::getId, ids);
            for (Serializable id : ids) {
                wrapper.or().apply(String.format("find_in_set(%s, ancestors)", id));
            }
        });
        return list(qw);
    }

    @Transactional(rollbackFor = Throwable.class)
    @Override
    public void delMenu(Collection<? extends Serializable> ids) {
        if (ObjectUtil.isEmpty(ids)) {
            return;
        }
        //删除, 删除当前以及当前所有的子集
        Set<Long> currentAndSubIds = findAllSubAndCurrentById(ids).stream().map(o -> o.getId()).collect(Collectors.toSet());
        this.baseDel(currentAndSubIds);
        roleLinkMenuService.delByMenuId(currentAndSubIds);
    }

    private List<Menu> findAllParentAndCurrentById(Collection<? extends Serializable> id) {
        if (ObjectUtil.isEmpty(id)) {
            return Collections.emptyList();
        }
        List<Menu> currentList = baseFindById(id);
        Set<Long> pIds = currentList.stream()
                .flatMap(o -> ConvertUtil.strToList(o.getAncestors(), Long.class).stream())
                .filter(o -> ObjectUtil.isNotEmpty(o))
                .map(o -> Long.parseLong(o.toString())).collect(Collectors.toSet());
        List<Menu> parentList = baseFindById(pIds);
        List<Menu> allList = new ArrayList<>();
        allList.addAll(currentList);
        allList.addAll(parentList);
        return allList;
    }

    @Override
    public List<Menu> find(Optional<String> keywords) {
        LambdaQueryWrapper<Menu> qw = buildLambdaQw();
        qw.orderByAsc(Menu::getSort);
        List<Menu> dbList;
        if (keywords.isPresent()) {
            qw.and(wrapper ->
                    wrapper.like(Menu::getName, keywords.get())
                            .or().like(Menu::getPath, keywords.get())
                            .or().like(Menu::getComponent, keywords.get())
                            .or().like(Menu::getRedirect, keywords.get())
            );
            dbList = findAllParentAndCurrentById(
                    list(qw).stream().map(o -> o.getId()).collect(Collectors.toList())
            );
        } else {
            dbList = list(qw);
        }
        return dbList;
    }


}
